#include "iso9660.h"

static void iso9660_inode_free(struct inode * inoptr);
static BOOLEAN iso9660_can_delete(struct inode * inoptr);
static int iso9660_truncate(struct inode * inoptr, off_t length);
static off_t iso9660_size(struct inode * inoptr);
static void iso9660_setsize(struct inode * inoptr, off_t size);
static mode_t iso9660_mode(struct inode * inoptr);
static void iso9660_setmode(struct inode * inoptr, mode_t mode);
static dev_t iso9660_realdev(struct inode * inoptr);
static BOOLEAN iso9660_minode(
	struct inode * inoptr, off_t pos, unsigned long block
	);
static unsigned long iso9660_bmap(struct inode * inoptr, off_t pos);
static BOOLEAN iso9660_noitem(struct inode * inoptr);
static BOOLEAN iso9660_additem(
	struct inode * inoptr, const char * name, int * error, ino_t ino
	);
static BOOLEAN iso9660_delitem(
	struct inode * inoptr, const char * name, int * error
	);
static ino_t iso9660_findson(struct inode * inoptr, const char * name);
static BOOLEAN iso9660_real_filename(
	char * page, struct inode * inoptr, int * error
	);
static uid_t iso9660_getuid(struct inode * inoptr);
static void iso9660_setuid(struct inode * inoptr, uid_t uid);
static gid_t iso9660_getgid(struct inode * inoptr);
static void iso9660_setgid(struct inode * inoptr, gid_t gid);
static void iso9660_mknod(struct inode * inoptr, dev_t dev);
static int iso9660_blocksize(struct inode * inoptr);
static off_t iso9660_max_filesize(struct inode * inoptr);
static void iso9660_update(
	struct inode * inoptr, time_t atime, time_t mtime, time_t ctime
	);
static nlink_t iso9660_change_nlinks(struct inode * inoptr, int change);
static int iso9660_get_name_of_son(
	struct inode * fino, ino_t inum, char * name, int size
	);
static int iso9660_stat(struct inode * inoptr, struct stat * statbuf);
static int iso9660_readdir(int fd, struct dirent * dirp);

struct inode_operation iso9660_inode_operation = {
	&iso9660_inode_free,
	&iso9660_can_delete,
	&iso9660_truncate,
	&iso9660_size,
	&iso9660_setsize,
	&iso9660_mode,
	&iso9660_setmode,
	&iso9660_realdev,
	&iso9660_real_filename,
	&iso9660_minode,
	&iso9660_noitem,
	&iso9660_additem,
	&iso9660_delitem,
	&iso9660_findson,
	&iso9660_getuid,
	&iso9660_setuid,
	&iso9660_getgid,
	&iso9660_setgid,
	&iso9660_mknod,
	&iso9660_bmap,
	&iso9660_blocksize,
	&iso9660_max_filesize,
	&iso9660_update,
	&iso9660_change_nlinks,
	&iso9660_get_name_of_son,
	&iso9660_stat,
	&iso9660_readdir,
};

static char * inode_type_names[] = { "", "socket", "pipe", "onhd" };

#define DECLARATION(type) \
	PRINTK("i'm a %s.\n", inode_type_names[(int)type])

static void iso9660_inode_free(struct inode * inoptr)
{
	kfree(inoptr->i_data);
	inoptr->i_data = NULL;
}

static BOOLEAN iso9660_can_delete(struct inode * inoptr)
{
	struct iso9660_inode * iinoptr;

	iinoptr = (struct iso9660_inode *)inoptr->i_data;
	if(iinoptr->i_nlinks != 1)
		DIE("BUG: cannot happen\n");

	return FALSE;
}

static int iso9660_truncate(struct inode *inoptr, off_t length)
{
	DECLARATION(inoptr->i_type);
	return 0;
}

static off_t iso9660_size(struct inode * inoptr)
{
	struct iso9660_inode * iinoptr;

	iinoptr = (struct iso9660_inode *)inoptr->i_data;

	return iinoptr->i_size;
}

static void iso9660_setsize(struct inode * inoptr, off_t size)
{
	DECLARATION(inoptr->i_type);
}

static mode_t iso9660_mode(struct inode * inoptr)
{
	struct iso9660_inode * iinoptr;

	iinoptr = (struct iso9660_inode *)inoptr->i_data;

	return iinoptr->i_mode;
}

static void iso9660_setmode(struct inode * inoptr, mode_t mode)
{
	DECLARATION(inoptr->i_type);
}

static dev_t iso9660_realdev(struct inode * inoptr)
{
	DECLARATION(inoptr->i_type);
	return NODEV;
}

static unsigned long iso9660_bmap(struct inode * inoptr, off_t pos)
{
	unsigned long block;
	struct iso9660_inode * iinoptr;

	iinoptr = (struct iso9660_inode *)inoptr->i_data;

	block = iinoptr->i_first_block;
	block += pos / CDROM_BLOCKSIZ;

	return block;
}

static BOOLEAN iso9660_minode(
	struct inode * inoptr, off_t pos, unsigned long blk
	)
{
	DECLARATION(inoptr->i_type);
	return FALSE;
}

static BOOLEAN iso9660_noitem(struct inode * inoptr)
{
	DECLARATION(inoptr->i_type);
	return TRUE;
}

static BOOLEAN iso9660_additem(
	struct inode * inoptr, const char * name, int * error, ino_t ino
	)
{
	DECLARATION(inoptr->i_type);
	return FALSE;
}

static BOOLEAN iso9660_delitem(
	struct inode * inoptr, const char * name, int * error
	)
{
	DECLARATION(inoptr->i_type);
	return FALSE;
}

static ino_t iso9660_findson(struct inode * inoptr, const char * name)
{
	off_t pos;
	int i, j, count;
	unsigned long block;
	struct buf_head * bh;
	struct iso9660_inode * iinoptr;
	struct iso_directory_record * rec;
	struct iso9660_super_block * isb;

	if(!strcmp(name, "."))
		return inoptr->i_num;

	if(!strcmp(name, "..")){
		if(inoptr->i_fnum == NOINO)
			DIE("BUG: cannot happen\n");

		return inoptr->i_fnum;
	}

	iinoptr = (struct iso9660_inode *)inoptr->i_data;
	isb = iinoptr->i_isb;
	
	for(pos = 0, j = 0; pos < iinoptr->i_size; pos += CDROM_BLOCKSIZ){
		block = iso9660_bmap(inoptr, pos);
		if(block == NOBLK){
			PRINTK("bmap error\n");
			return NOINO;
		}

		bh = bread(inoptr->i_dev, block);
		if(!bh){
			PRINTK("bread error\n");
			return NOINO;
		}

		count = CDROM_BLOCKSIZ;
		if(iinoptr->i_size - pos < count)
			count = iinoptr->i_size - pos;

		for(i = 0; i < count; i += rec->length){
			if(count - i < sizeof(struct iso_directory_record))
				break;

			rec = (struct iso_directory_record *)&bh->b_data[i];
			if(!rec->length)
				break;

			if(rec->length < sizeof(struct iso_directory_record)){
				brelse(bh);
				return NOINO;
			}

			j += 1;
			if(j <= 2)
				continue;

			if(!strncmp(name, (char *)rec->name, rec->name_len)){
				brelse(bh);

				if(isb->root_directory_record.first_sector
					!= rec->first_sector)
					return block * CDROM_BLOCKSIZ + i;

				return ROOT_INODE;
			}
		}

		brelse(bh);
	}

	return NOINO;
}

static BOOLEAN iso9660_real_filename(
	char * page, struct inode * inoptr, int * error
	)
{
	DECLARATION(inoptr->i_type);
	return FALSE;
}

static uid_t iso9660_getuid(struct inode * inoptr)
{
	struct iso9660_inode * iinoptr;

	iinoptr = (struct iso9660_inode *)inoptr->i_data;

	return iinoptr->i_uid;
}

static void iso9660_setuid(struct inode * inoptr, uid_t uid)
{
	DECLARATION(inoptr->i_type);
}

static gid_t iso9660_getgid(struct inode * inoptr)
{
	struct iso9660_inode * iinoptr;

	iinoptr = (struct iso9660_inode *)inoptr->i_data;

	return iinoptr->i_gid;
}

static void iso9660_setgid(struct inode * inoptr, gid_t gid)
{
	DECLARATION(inoptr->i_type);
}

static void iso9660_mknod(struct inode * inoptr, dev_t dev)
{
	DECLARATION(inoptr->i_type);
}

static int iso9660_blocksize(struct inode * inoptr)
{
	return CDROM_BLOCKSIZ;
}

static off_t iso9660_max_filesize(struct inode * inoptr)
{
	DECLARATION(inoptr->i_type);
	return 0;
}

static void iso9660_update(
	struct inode * inoptr, time_t atime, time_t mtime, time_t ctime
	)
{

}

static nlink_t iso9660_change_nlinks(struct inode * inoptr, int change)
{
	DECLARATION(inoptr->i_type);
	return 0;
}

static int iso9660_get_name_of_son(
	struct inode * fino, ino_t inum, char * name, int size
	)
{
	int offset;
	unsigned long block;
	struct buf_head * bh;
	struct iso_directory_record * rec;

	block = inum / CDROM_BLOCKSIZ;
	offset = inum % CDROM_BLOCKSIZ;

	bh = bread(fino->i_dev, block);
	if(!bh){
		PRINTK("bread error\n");
		return -EIO;
	}

	rec = (struct iso_directory_record *)&bh->b_data[offset];

	memcpy(name, rec->name, rec->name_len);
	name[rec->name_len] = '\0';

	brelse(bh);

	return 0;
} 

static int iso9660_stat(struct inode * inoptr, struct stat * statbuf)
{
	struct iso9660_super_block * isb;
	struct iso9660_inode * iinoptr = (struct iso9660_inode *)inoptr->i_data;

	statbuf->st_dev = inoptr->i_dev;

	iinoptr = (struct iso9660_inode *)inoptr->i_data;
	isb = iinoptr->i_isb;

	statbuf->st_ino = inoptr->i_num;
	statbuf->st_mode = iinoptr->i_mode;
	statbuf->st_nlink = iinoptr->i_nlinks;
	statbuf->st_uid = iinoptr->i_uid;
	statbuf->st_gid = iinoptr->i_gid;

	statbuf->st_size = iinoptr->i_size;
	statbuf->st_blksize = CDROM_BLOCKSIZ;

	statbuf->st_blocks = (iinoptr->i_size + 511) / 512;
	statbuf->st_atime = iinoptr->i_atime;
	statbuf->st_atime_nsec = 0;
	statbuf->st_mtime = iinoptr->i_mtime;
	statbuf->st_mtime_nsec = 0;
	statbuf->st_ctime = iinoptr->i_ctime;
	statbuf->st_ctime_nsec = 0;

	return 0;
}

static int iso9660_readdir(int fd, struct dirent * dirp)
{
	int ret;
	ino_t ino;
	off_t off;
	struct filp * fp;
	struct inode * inoptr;
	struct iso9660_inode * iinoptr;
	struct iso9660_super_block * isb;
	struct iso_directory_record rec;

	fp = fget(fd);
	off = fp->f_pos;

	if(CDROM_BLOCKSIZ - (off % CDROM_BLOCKSIZ)
		< sizeof(struct iso_directory_record))
		off += CDROM_BLOCKSIZ - (off % CDROM_BLOCKSIZ);

again:
	do_lseek(fd, off, SEEK_SET);

	ret = do_read(fd, (char *)&rec, sizeof(struct iso_directory_record));
	if(ret < 0)
		return ret;

	if(!ret)
		return 0;

	if(ret != sizeof(struct iso_directory_record))
		return -EIO;

	if(!rec.length){
		off = fp->f_pos;
		off += CDROM_BLOCKSIZ - (off % CDROM_BLOCKSIZ);
		goto again;
	}

	if(rec.length < sizeof(struct iso_directory_record))
		return -EIO;

	ret = do_read(fd, dirp->d_name, rec.name_len);
	if(ret < 0)
		return ret;

	if(ret != rec.name_len)
		return -EIO;

	do_lseek(fd, off + rec.length, SEEK_SET);

	inoptr = fget(fd)->f_ino;
	iinoptr = (struct iso9660_inode *)inoptr->i_data;
	isb = iinoptr->i_isb;

	ino = NOINO;

	if(rec.name_len == 1){
		if(dirp->d_name[0] == 0x00){
			ino = inoptr->i_num;
			rec.name_len = 1;
			dirp->d_name[0] = '.';
		}else if(dirp->d_name[0] == 0x01){
			if(inoptr->i_fnum == NOINO)
				DIE("BUG: cannot happen\n");

			ino = inoptr->i_fnum;
			rec.name_len = 2;
			dirp->d_name[0] = '.';
			dirp->d_name[1] = '.';
		}
	}

	if(ino == NOINO){
		if(isb->root_directory_record.first_sector != rec.first_sector)
			ino = off;
		else
			ino = ROOT_INODE;
	}

	if(put_user4(&dirp->d_ino, ino) < 0)
			return -EFAULT;

	if(put_user4(&dirp->d_off, fget(fd)->f_pos) < 0)
		return -EFAULT;

	if(put_user2(&dirp->d_reclen,
		((nameoff + rec.name_len + 1) + 3) & (~3)) < 0)
		return -EFAULT;

	if(put_user1(&dirp->d_name[rec.name_len], '\0') < 0)
		return -EFAULT;

	return rec.length;
}
